﻿using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Numerics;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Diagnostics;
using System.Collections.Concurrent;

namespace xrwang.DDNS
{
    /// <summary>
    /// 工具类
    /// </summary>
    public static class Utility
    {
        /// <summary>
        /// 最近的公有实际IPv4地址字典，键为云服务提供商，值为IPv4地址
        /// </summary>
        private static ConcurrentDictionary<CSPEnum, IPAddress> LastPublicRealIPv4Addresses = new ConcurrentDictionary<CSPEnum, IPAddress>();
        /// <summary>
        /// 最近的公有实际IPv6地址字典，键为云服务提供商，值为IPv6地址
        /// </summary>
        private static ConcurrentDictionary<CSPEnum, IPAddress> LastPublicRealIPv6Addresses = new ConcurrentDictionary<CSPEnum, IPAddress>();
        /// <summary>
        /// 最近更新IPv4地址的时间字典，键为云服务提供商，值为更新时间
        /// </summary>
        private static ConcurrentDictionary<CSPEnum, DateTime?> LastUpdateIPv4DDNSTimes = new ConcurrentDictionary<CSPEnum, DateTime?>();
        /// <summary>
        /// 最近更新IPv6地址的时间字典，键为云服务提供商，值为更新时间
        /// </summary>
        private static ConcurrentDictionary<CSPEnum, DateTime?> LastUpdateIPv6DDNSTimes = new ConcurrentDictionary<CSPEnum, DateTime?>();

        /// <summary>
        /// 设置文件名
        /// </summary>
        public const string SETTING_FILE_NAME = "ddns.json";

        /// <summary>
        /// 私有IP地址范围
        /// </summary>
        private static readonly IPAddressRange[] privateIPAddressRanges = new IPAddressRange[]
        {
            new IPAddressRange("10.0.0.0", "10.255.255.255"),
            new IPAddressRange("100.64.0.0", "100.127.255.255"),
            new IPAddressRange("127.0.0.0", "127.255.255.255"),
            new IPAddressRange("169.254.0.0", "169.254.255.255"),
            new IPAddressRange("172.29.144.0", "172.29.144.255"),
            new IPAddressRange("192.168.0.0", "192.168.255.255"),
            new IPAddressRange("255.255.255.255", "255.255.255.255"),
            new IPAddressRange("FEC0:0000:0000:0000:0000:0000:0000:0000", "FEC0:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"),
            new IPAddressRange("FE80:0000:0000:0000:0000:0000:0000:0000", "FE80:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF")
        };

        /// <summary>
        /// 尝试获取公有实际IPv4地址的服务器列表
        /// </summary>
        private static readonly string[] ipv4Servers = new string[]
        {
            "https://api.ipify.org",
            "https://ifconfig.me/ip",
            "http://v4.ip.zxinc.org/info.php?type=json",
            "https://ipv4.lookup.test-ipv6.com/ip/?callback=_jqjsp",
            "https://ipv4.vm3.test-ipv6.com/ip/?callback=_jqjsp",
            "http://ip-api.com/json"
        };

        /// <summary>
        /// 尝试获取公有实际IPv6地址的服务器列表
        /// </summary>
        private static readonly string[] ipv6Servers = new string[]
        {
            "https://api64.ipify.org",
            "http://v6.ip.zxinc.org/info.php?type=json",
            "https://ipv6.lookup.test-ipv6.com/ip/?callback=_jqjsp",
            "https://ipv6.vm3.test-ipv6.com/ip/?callback=_jqjsp",
            "https://mtu1280.vm3.test-ipv6.com/ip/?callback=_jqjsp",
            "https://ds.v6ns.vm3.test-ipv6.com/ip/?callback=_jqjsp",
            "https://ds.vm3.test-ipv6.com/ip/?callback=_jqjsp"
        };

        /// <summary>
        /// HttpClient对象
        /// </summary>
        private static HttpClient httpClient = new HttpClient(new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        });

        /// <summary>
        /// 获取公有实际IPv4地址
        /// </summary>
        /// <returns></returns>
        public static async Task<IPAddress> GetPublicRealIPv4Address()
        {
            IEnumerable<IPAddress> ips = GetPublicIPAddresses(AddressFamily.InterNetwork);
            if (ips == null || !ips.Any())
                return null;
            IPAddress realIPAddress = await GetRealIPv4Address();
            return realIPAddress == null || !ips.Any(ip => ip.Equals(realIPAddress)) ? null : realIPAddress;
        }

        /// <summary>
        /// 获取公有实际IPv6地址
        /// </summary>
        /// <returns></returns>
        public static async Task<IPAddress> GetPublicRealIPv6Address()
        {
            IEnumerable<IPAddress> ips = GetPublicIPAddresses(AddressFamily.InterNetworkV6);
            if (ips == null || !ips.Any())
                return null;
            IPAddress realIPAddress = await GetRealIPv6Address();
            return realIPAddress == null || !ips.Any(ip => ip.Equals(realIPAddress)) ? null : realIPAddress;
        }

        /// <summary>
        /// 获取本机的公有IP地址列表
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IPAddress> GetPublicIPAddresses()
        {
            if (!NetworkInterface.GetIsNetworkAvailable())
                return null;
            return NetworkInterface.GetAllNetworkInterfaces()
                .Where(ni => ni.OperationalStatus == OperationalStatus.Up && ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
                .Select(p => p.GetIPProperties())
                .SelectMany(p => p.UnicastAddresses)
                .Where(p => (p.Address.AddressFamily == AddressFamily.InterNetwork || p.Address.AddressFamily == AddressFamily.InterNetworkV6) &&
                    !IPAddress.IsLoopback(p.Address) && !p.Address.IsIPv6UniqueLocal && !p.Address.IsIPv6LinkLocal &&
                    !p.Address.IsIPv6SiteLocal && !IsPrivateIPAddress(p.Address))
                .Select(p => p.Address);
        }

        /// <summary>
        /// 获取本机的公有IP地址列表
        /// </summary>
        /// <param name="addressFamily">地址族，可以取值InterNetwork或者InterNetworkV6，分别代表IPv4地址和IPv6地址</param>
        /// <returns></returns>
        public static IEnumerable<IPAddress> GetPublicIPAddresses(AddressFamily addressFamily)
        {
            if (!NetworkInterface.GetIsNetworkAvailable())
                return null;
            return NetworkInterface.GetAllNetworkInterfaces()
                .Where(ni => ni.OperationalStatus == OperationalStatus.Up && ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
                .Select(p => p.GetIPProperties())
                .SelectMany(p => p.UnicastAddresses)
                .Where(p => p.Address.AddressFamily == addressFamily &&
                    !IPAddress.IsLoopback(p.Address) && !p.Address.IsIPv6UniqueLocal && !p.Address.IsIPv6LinkLocal &&
                    !p.Address.IsIPv6SiteLocal && !IsPrivateIPAddress(p.Address))
                .Select(p => p.Address);
        }

        /// <summary>
        /// 获取IP地址的值
        /// </summary>
        /// <param name="iPAddress"></param>
        /// <returns></returns>
        public static BigInteger GetIPAddressValue(this IPAddress iPAddress)
        {
            List<byte> bytes = iPAddress.GetAddressBytes().Reverse().ToList();
            bytes.Add(0);
            return new BigInteger(bytes.ToArray());
        }

        /// <summary>
        /// 判断IP地址是否为私有IP地址
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public static bool IsPrivateIPAddress(this IPAddress ip)
        {
            BigInteger ipValue = GetIPAddressValue(ip);
            return privateIPAddressRanges.Any(r => r.IsInRange(ipValue));
        }

        /// <summary>
        /// 获取实际的IPv4地址
        /// </summary>
        /// <returns></returns>
        public static async Task<IPAddress> GetRealIPv4Address()
        {
            foreach (string ipv4Server in ipv4Servers)
            {
                string json = await Request(ipv4Server);
                IPAddress ip = ParseIPAddress(json, ipv4Server);
                if (ip != null)
                    return ip;
            }
            return null;
        }

        /// <summary>
        /// 获取实际的IPv6地址
        /// </summary>
        /// <returns></returns>
        public static async Task<IPAddress> GetRealIPv6Address()
        {
            foreach (string ipv6Server in ipv6Servers)
            {
                string json = await Request(ipv6Server);
                IPAddress ip = ParseIPAddress(json, ipv6Server);
                if (ip != null)
                    return ip;
            }
            return null;
        }

        /// <summary>
        /// 向提供返回实际IP地址的服务器发送请求，获取响应内容
        /// </summary>
        /// <param name="url">提供返回实际IP地址的服务器url</param>
        /// <returns>返回响应内容</returns>
        private static async Task<string> Request(string url)
        {
            try
            {
                return await httpClient.GetStringAsync(url);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("在请求公用IP地址时发生异常。{0}服务器地址：{1}{0}异常：{2}",
                    Environment.NewLine, url, ex);
                return string.Empty;
            }
        }

        /// <summary>
        /// 解析IP地址
        /// </summary>
        /// <param name="json">从提供返回实际IP地址的服务器获取到的响应</param>
        /// <param name="url">提供返回实际IP地址的服务器url</param>
        /// <returns></returns>
        private static IPAddress ParseIPAddress(string json, string url)
        {
            if (string.IsNullOrWhiteSpace(json))
                return null;
            IPAddress ip;
            if (IPAddress.TryParse(json, out ip))
                return ip;
            string address = string.Empty;
            if (url.Contains("test-ipv6.com"))
            {
                string prefix = "_jqjsp(";
                if (json.StartsWith(prefix))
                    json = json.Substring(prefix.Length);
                string suffix = ")";
                if (json.EndsWith(suffix))
                    json = json.Substring(0, json.Length - suffix.Length);
                JsonNode node = JsonNode.Parse(json);
                if (node != null)
                {
                    node = node["ip"];
                    if (node != null)
                        address = node.GetValue<string>();
                }
            }
            else if (url.Contains("ip-api.com"))
            {
                JsonNode node = JsonNode.Parse(json);
                if (node != null)
                {
                    node = node["query"];
                    if (node != null)
                        address = node.GetValue<string>();
                }
            }
            else if (url.Contains("zxinc.org"))
            {
                JsonNode node = JsonNode.Parse(json);
                if (node != null)
                {
                    node = node["data"];
                    if (node != null)
                    {
                        node = node["myip"];
                        if (node != null)
                            address = node.GetValue<string>();
                    }
                }
            }
            if (string.IsNullOrWhiteSpace(address))
                return null;
            if (IPAddress.TryParse(address, out ip))
                return ip;
            return null;
        }

        /// <summary>
        /// 从字符串获取域名记录类型
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static DomainRecordTypeEnum StringToRecordType(string value)
        {
            DomainRecordTypeEnum type;
            if (Enum.TryParse(value, true, out type))
                return type;
            if (value == "显性URL")
                return DomainRecordTypeEnum.REDIRECT_URL;
            else if (value == "隐形URL")
                return DomainRecordTypeEnum.FORWARD_URL;
            else
                return DomainRecordTypeEnum.A;
        }

        /// <summary>
        /// 网络发生变化时的事件处理程序
        /// </summary>
        /// <param name="cancellationToken">取消通知</param>
        /// <returns></returns>
        public static async Task HandleNetworkChanged(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
                return;
            //获取公有实际IP地址
            IPAddress ipv4Address = await GetPublicRealIPv4Address();
            IPAddress ipv6Address = await GetPublicRealIPv6Address();
            if (ipv4Address == null && ipv6Address == null)
                return;
            //读取配置
            List<DDNSInfo> infos = await ReadSetting(cancellationToken);
            if (infos == null || infos.Count == 0)
                return;
            //处理域名记录
            foreach (DDNSInfo info in infos)
            {
                await HandleDomainRecord(ipv4Address, ipv6Address, info, cancellationToken);
            }
        }

        /// <summary>
        /// 读取设置
        /// </summary>
        /// <param name="cancellationToken">取消通知</param>
        /// <returns>返回DDNS信息列表</returns>
        private static async Task<List<DDNSInfo>> ReadSetting(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
                return null;
            JsonSerializerOptions options = new JsonSerializerOptions()
            {
                ReadCommentHandling = JsonCommentHandling.Skip,
                AllowTrailingCommas = true,
                NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
            };
            using (FileStream fs = new FileStream(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, SETTING_FILE_NAME), FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                try
                {
                    return await JsonSerializer.DeserializeAsync<List<DDNSInfo>>(fs, options, cancellationToken);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                    Console.WriteLine(ex.Message);
                    return null;
                }
            }
        }

        /// <summary>
        /// 处理域名记录
        /// </summary>
        /// <param name="ipv4Address">公有实际IPv4地址</param>
        /// <param name="ipv6Address">公有实际IPv6地址</param>
        /// <param name="info">配置</param>
        /// <param name="cancellationToken">取消通知</param>
        /// <returns></returns>
        private static async Task HandleDomainRecord(IPAddress ipv4Address, IPAddress ipv6Address, DDNSInfo info, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
                return;
            IEnumerable<DomainRecordInfo> records = info.DomainRecords == null ? null :
                info.DomainRecords.Where(rec => (ipv4Address != null && IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.A)) ||
                    (ipv6Address != null && IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.AAAA)));
            if (records == null || !records.Any())
                return;
            IDomainRecordOperator dro = null;
            CSPEnum csp;
            if (Enum.TryParse<CSPEnum>(info.CSP, true, out csp))
            {
                switch (csp)
                {
                    case CSPEnum.阿里云:
                        dro = new Aliyun.DomainRecordOperator(info.AccessKeyID, info.AccessKeySecret);
                        break;
                    case CSPEnum.腾讯云:
                        dro = new TencentCloud.DomainRecordOperator(info.AccessKeyID, info.AccessKeySecret);
                        break;
                    case CSPEnum.华为云:
                        dro = new HuaweiCloud.DomainRecordOperator(info.AccessKeyID, info.AccessKeySecret, info.ProjectId);
                        break;
                    case CSPEnum.DNSExit:
                        dro = new DNSExit.DomainRecordOperator(info.AccessKeyID);
                        break;
                    //todo:添加其他云服务提供商
                    default:
                        break;
                }
            }
            if (dro == null)
                return;
            foreach (DomainRecordInfo rec in records)
            {
                if (cancellationToken.IsCancellationRequested)
                    return;
                IPAddress lastIPv4Address, lastIPv6Address;
                LastPublicRealIPv4Addresses.TryGetValue(csp, out lastIPv4Address);
                LastPublicRealIPv6Addresses.TryGetValue(csp, out lastIPv6Address);
                if (IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.A) && ipv4Address != null && ipv4Address != lastIPv4Address)
                    rec.Value = ipv4Address.ToString();
                else if (IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.AAAA) && ipv6Address != null && ipv6Address != lastIPv6Address)
                    rec.Value = ipv6Address.ToString();
                else
                    continue;
                ActionResult<string, string> result = await dro.SaveAsync(rec);
                if (result.Success)
                {
                    if (IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.A))
                    {
                        LastPublicRealIPv4Addresses.TryAdd(csp, ipv4Address);
                        LastUpdateIPv4DDNSTimes.TryAdd(csp, DateTime.Now);
                    }
                    else if (IsSameDomainRecordType(rec.Type, DomainRecordTypeEnum.AAAA))
                    {
                        LastPublicRealIPv6Addresses.TryAdd(csp, ipv6Address);
                        LastUpdateIPv6DDNSTimes.TryAdd(csp, DateTime.Now);
                    }
                }
                string message = string.Format("{0}\t更新域名{1}，原因：{2}。{3}{4}",
                    DateTime.Now, result.Success ? "成功" : "失败", result.Message, Environment.NewLine, rec);
                Debug.WriteLine(message);
                Console.WriteLine(message);
            }
        }

        /// <summary>
        /// 比较是否为相同的域名记录类型
        /// </summary>
        /// <param name="type"></param>
        /// <param name="recordType"></param>
        /// <returns></returns>
        private static bool IsSameDomainRecordType(string type, DomainRecordTypeEnum recordType)
        {
            return string.Compare(type, recordType.ToString("g"), true) == 0;
        }
    }
}